package pinboard.dataLayer.net;

import android.util.Log;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import java.net.InetSocketAddress;

public abstract class BaseClientPipe {

    protected Object netStateLock = new Object();

    protected ClientPipeListener clientPipeListener = null;

    protected NetAddressInfo netAddressInfo = null;

    protected IoConnector innerConnector = null;

    protected IoSession innerSession = null;

    protected int state;
    public final int initState = -1;//未连接
    public final int disconnectedState = -2;//断网
    public final int notConnectedState = -3;//连接失败
    public final int connectingState = 1;//连接中
    public final int connectedState = 2;//已连接

    public BaseClientPipe(NetAddressInfo address, ClientPipeListener listener) {
        this.state = initState;

        this.netAddressInfo = address;
        this.clientPipeListener = listener;
        innerConnector = new NioSocketConnector();
        initHandler(innerConnector);
        initListener(innerConnector);
    }

    public BaseClientPipe(NetAddressInfo address) {
        this(address, null);
    }

    public NetAddressInfo getAddressInfo() {
        return netAddressInfo;
    }

    public int getState() {//-1未连接，-2断网，-3连接失败，1连接中，2已连接
        synchronized (netStateLock) {
            return state;
        }
    }

    private Integer seqSeed = -1;

    public String getSequence() {
        synchronized (netStateLock) {
            seqSeed = seqSeed + 1;
        }
        return netAddressInfo.getName() + seqSeed;
    }

    public void connect(int timeOutMillis, ActionCallback<PipeContext> callback) {
        synchronized (netStateLock) {
            state = connectingState;
        }
        connectImp(timeOutMillis, 0, callback);
    }

    private void connectImp(int timeOutMillis, int retryTimes, ActionCallback<PipeContext> callback) {
        PipeContext context = new PipeContext();
        context.setRetryTimes(retryTimes);
        try {
            if (timeOutMillis < 0) {
                timeOutMillis = 1000;
            }
            innerConnector.setConnectTimeoutMillis(timeOutMillis);
            ConnectFuture future = innerConnector.connect(new InetSocketAddress(netAddressInfo.getHost(), netAddressInfo.getPort()));
            future.awaitUninterruptibly();

            IoSession session = future.getSession();
            if (session != null && session.isConnected()) {
                synchronized (netStateLock) {
                    innerSession = session;
                    state = connectedState;
                }
                context.setNetState(true);
                invokeConnectSuccess(callback, context);
            } else {
                synchronized (netStateLock) {
                    state = notConnectedState;
                }
                context.setNetState(false);
                invokeConnectError(callback, context);
            }

        } catch (Exception e) {
            synchronized (netStateLock) {
                state = notConnectedState;
            }
            invokeConnectError(callback, context);
        }
    }

    protected void invokeConnectSuccess(ActionCallback<PipeContext> callback, PipeContext context) {
        if (callback != null) {
            safeInvokeCallback(callback, context);
        }
    }

    protected void invokeConnectError(ActionCallback<PipeContext> callback, PipeContext context) {
        if (callback != null) {
            safeInvokeCallback(callback, context);
            processContext(callback, context);
        }
    }

    protected void safeInvokeCallback(ActionCallback<PipeContext> callback, PipeContext context) {
        try {
            callback.invoke(this, context);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void processContext(ActionCallback<PipeContext> callback, PipeContext context) {
        if (context.getRetry()) {
            int waitMillis = context.getWaitMillis();
            if (waitMillis > 0) {
                try {
                    Thread.sleep(waitMillis);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            connectImp(context.getConnectTimeoutMillis(), context.getRetryTimes() + 1, callback);
        }
    }

    protected void invokeDisconnected(IoSession ioSession) {
        Log.e("BaseClientPipe", "###sessionDestroyed###");
        boolean canDo = false;
        synchronized (netStateLock) {
            if (innerSession == ioSession) {
                canDo = true;
                innerSession = null;
                state = disconnectedState;
            }
        }
        if (canDo && this.clientPipeListener != null) {
            this.clientPipeListener.onDisconnect(this);
        }
    }

    protected abstract void initHandler(final IoConnector connector);

    protected void initListener(final IoConnector connector) {

        connector.addListener(new IoServiceListenerAdapter() {
            @Override
            public void sessionDestroyed(IoSession ioSession) {
                BaseClientPipe.this.invokeDisconnected(ioSession);
            }
        });
    }

    public boolean sendBytes(byte[] data) {
        boolean canSend = false;
        synchronized (netStateLock) {
            canSend = (this.state == connectedState) && this.innerSession != null && this.innerSession.isConnected();
            if (canSend) {
                this.innerSession.write(IoBuffer.wrap(data));//MaybeError
                return true;
            } else {
                return false;
            }
        }
    }
}
